package com.zwh.se.string;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;

/**
 * @author yexi
 * String本质上是一个char类型数组的封装,char[]被final所修饰所以一旦初始化就不可变,而类自身也被final所修饰,所以也不可被继承
 */
public class StringDemo {
    public static void main(String[] args) {

        System.out.println("--------------------[0]创建对象的方式--------------------");
        //构造器的底层就是给对象底层的value数组进行赋值操作。
        String str1 = new String();//普通的初始化没有意义,因为value[]被final所修饰,字符串不可变
        String str2 = new String("abc");
        String str3 = new String(new char[]{'a', 'b', 'c'});
        String str4 = "abc";
        String str5 = new String(new char[0]);
        //java中存在着运行时常量池的内存模型概念,而在上面中,当使用字面量方式去创建对象时,本质上就是让栈中的该变量指向了常量池之中对应值的对应地址
        //而你通过new所创建的对象,则是在堆区中开辟了空间,因此双方是不一样的
        //当然,本质上每次new的时候,地址就已经不一致了
        System.out.println(str1 == str5);//false,虽然同样是空字符序列,但是双方的地址是不一样的
        System.out.println(str1.equals(str5));//true,若真要对比对象的话,就不能通过==的地址判断,而是通过重写后的equals的逻辑判断
        System.out.println(str2 == str3);//false
        System.out.println(str2 == str4);//false
        System.out.println(str2.equals(str3) && str2.equals(str4));//true
        //运行时常量池的好处：
        String str6 = "abc";
        System.out.println(str4 == str6);//true
        //由于使用字面量来创建对象,会去常量池中找到对应值的对应地址,所以str4和str6本质上指向的是同一个地址,这样就是常量池的最大好处:复用
        //因此一般而言,没有特别的需求的话,String不需要使用new创建对象


        System.out.println("--------------------[1]字符串运算对比较的影响--------------------");
        String str8 = "" + "abc";//两个字面量的拼接,拼接后仍在常量区
        System.out.println(str8 == str4);//true
        String str9 = str4 + "";//运算时,由于不知道str4是一个什么变量,所以编译器不会对拼接进行优化,因此它们会进入到堆中,在堆里面进行运算
        System.out.println(str4 == str9);//false

        //特殊的字符拼接
        System.out.println(null + "");

        System.out.println("--------------------[2]字符串的方法演示--------------------");

        //获取字符串的长度
        System.out.println("abc".length());

        //字符串是否为空
        System.out.println("".isEmpty());

        //获取指定索引下标所对应的字符
        System.out.println("abc".charAt(1));

        //通过逻辑判断来比较两个String,忽略大小写
        System.out.println("abc".equalsIgnoreCase("ABC"));

        //将此 String 中的所有字符都转换为大写。
        System.out.println("abc".toUpperCase());

        //将此 String 中的所有字符都转换为小写。
        System.out.println("ABC".toLowerCase());

        //String replace(char oldChar, char newChar)
        //将指定字符串中出现的所有 oldChar 得到的替换成 newChar ,再返回一个新的字符串
        String str = "aabc stdf";
        String newsetr = str.replace(' ', '\u0000');
        System.out.println(newsetr);

        //String[] split(String regex)
        //根据给定正则表达式的匹配来拆分此字符串,并分割成各个字符串数组
        String strs = "132-2342-1234";
        String[] arr = strs.split("-");
        System.out.println(Arrays.toString(arr));
        //衍生用法:也可以这样用来分割所有字符
        String[] arr2 = strs.split("");
        System.out.println(Arrays.toString(arr2));
        //衍生用法:当不存在可分割的字符时，将自动省略，所以只分割成了132
        String newStrs = "1,3,2,,,,,";
        String[] split = newStrs.split(",");
        System.out.println(Arrays.toString(split));

        //String substring(int beginIndex, int endIndex)
        //将给定字符串从 beginIndex 到 endIndex 截取成子串再返回，含头不含尾。
        // 比如这个"132-2342-1234"，是截取成"-23"返回，字符串的索引从0开始
        //TIPS:以后再java看到类似的参数都是含头不含尾：beginIndex,endIndex  begin,end  fromIndex,toIndex  from,to
        System.out.println(strs.substring(3, 6));
        //只传一个表示只传了 beginIndex ，因此将从 beginIndex 直接截取到最后返回
        System.out.println(strs.substring(3));

        //截去字符串开头和末尾的空白并返回(开头结尾是多个空白时就全去掉)
        System.out.println("  v sdg b  ".trim());

        //String类实现了Comparable,compareTo的效果是String重写后的
        //如果参数字符串等于该字符串，则值为0
        System.out.println("abc".compareTo("abc"));
        //如果该字符串与参数字符串存在个位差异,则返回那个位置的ASCII差值
        System.out.println("acd".compareTo("abcdef"));
        //如果该字符串与参数字符串存在长度差异,则返回双方长度的差值
        System.out.println("abc".compareTo("abcdef"));

        //字符串的合并/拼接
        System.out.println("a".concat("bc"));

        //将传输参数转换为String类型(toString是各个类自己重写的方法,这个是String自己的方法,而且还可以传基本类型)
        System.out.println(String.valueOf(false));
        //当传递的值为null时则返回一个"null"字符串
        System.out.println(String.valueOf((String)null));

        //从左往右找到给定字符或字符串第一次出现的索引位置
        System.out.println("2.001".indexOf("."));
        System.out.println("2.001".indexOf("01"));

        //从右往左找到给定字符或字符串第一次出现的索引位置
        System.out.println("2.001".lastIndexOf("0"));

        //返回由CharSequence elements的副本和指定delimiter的副本连接在一起的新字符串。
        //join(CharSequence delimiter, CharSequence... elements)
        System.out.println(String.join(",", "first", "second", "third"));//first,second,third
        //join(CharSequence delimiter, Iterable<? extends CharSequence> elements)
        ArrayList<String> list = new ArrayList<>();
        Collections.addAll(list, "first", "second", "third");
        System.out.println(String.join(",", list));//first,second,third

        System.out.println("--------------------[3]String的内存分析--------------------");
        //这种字面量的拼接,实际上会经过编译器的优化,最终合并为完整的字符串,这点我们可以从反编译文件里面看到
        String s1 = "a" + "b" + "c";
        System.out.println(String.class.getName() + "@" + Integer.toHexString(System.identityHashCode(s1)));
        //而对于常量池而言,常量池的一个特点是,初始化时String会先从常量池里面找对应字符串,第一次如果没有这个字符串,就放进去,而下次如果找到,就直接从常量池中取
        String s2 = "ab" + "c";
        System.out.println(String.class.getName() + "@" + Integer.toHexString(System.identityHashCode(s2)));
        //之前也说过,new是在堆中开辟出空间的意思,然后再根据传入的字符串,做跟上面对常量池描述一样的事情
        //也就是说,如果是new出这样的常量池里面没有的常量,则会一次性开辟两个空间（1.字符串常量池中的字符串 2.堆中的开辟的空间）
        String s3 = new String("abc");
        System.out.println(String.class.getName() + "@" + Integer.toHexString(System.identityHashCode(s3)));
        //有变量参与的字符串拼接
        String s4 = "ab";
        String s5 = s4 + "c";
        System.out.println(String.class.getName() + "@" + Integer.toHexString(System.identityHashCode(s5)));
        //在编译期,由于编译期不知道s4是一个字符串,所以编译器并不会进行优化
        //事实上,在查看反编译文件之后,可以看到这种运算的拼接,底层竟然是用StringBuilder来实现的


    }
}
